/********************************************
 * MODUL
 * 
 * Name: LOGD
 * 
 * Zweck: Hauptmodul. Es verwaltet die Summary und Proxy Prozesse, startet und beendet diese.
 * 
 * Autor(en): Simon Hinderhofer, Robert Lehmann, Benjamin Reiner, TIT08
 * 
 ********************************************/

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>	/* für waitpid */
#include <string.h>
#include <unistd.h>	/* für getopt */
#include <signal.h>	/* für SIGINT */
#include <stdlib.h>	/* für atoi */
#include <errno.h>	/* für errno */

#include "logd.h"
#include "summary_process.h"
#include "proxy_process.h"
#include "hex_print.h"
#include "tcp_socket_helper.h"

#define TRUE    1
#define FALSE   0

int finished = FALSE;
pid_t summary_pid;
FILE* file;
int server_socket, client_socket;
int sum_proxy_processes_logd = 0;

/********************************************
 * FUNKTION
 * 
 * Name: print_help
 * 
 * Zweck: Gibt beim Aufruf die Hilfe-Informationen auf der Kommandozeile aus
 * 
 * Parameter:
 *			nicht vorhanden
 * 
 * Globale Variablen:
 *			nicht vorhanden
 * 	
 * Rückgabewert:
 *			nicht vorhanden
 * 	
 * 
 ********************************************/
void print_help()
{
  printf("Testat TIT08 6. Semester\n");
  printf("Gruppe: Robert Lehmann, Simon Hinderhofer, Benjamin Reiner\n\n");
  printf("-s host:port     Definiert die Socketadresse des entfernten Servers, an den alle Anfragen weitergeleitet werden.\n");
  printf("-f file          Protokolliert saemtliche Anfragen und Antworten in die Datei file. Wird als Dateiname \"-\" verwendet, so ist die Standardausgabe (stdout) zu verwenden.\n");
  printf("-p port          Definiert den lokalen Port, auf dem Server auf Anrufe wartet.\n");
  printf("-t secs          Definiert die Zeitspanne in Sekunden (Timeout), nach der der Server die Verbindung zu einem inaktiven Client abbricht.\n");
  printf("-h               Gibt die Hilfe aus.\n");
}


/********************************************
 * FUNKTION
 * 
 * Name:  sig_handler
 * 
 * Zweck: Fängt Signale ab. Ist für das richtige beenden der Prozesse zuständig.
 * 
 * Parameter:
 *		int signo: 
 * 
 * Globale Variablen:
 * 	
 * Rückgabewert:
 * 			nicht vorhanden
 * 
 ********************************************/
static void sig_handler(int signo)
{
  int pid = 0;
  
  /* Bei SIGINT werden erst alle Proxy Prozesse mit Signal SIGUSR2 beendet.
   * Anschließend wird der Summary Prozess mit SIGUSR1 beendet */
  if (signo == SIGINT) 
  {
  finished = TRUE;
  signal(SIGINT, SIG_DFL);		/* dass das Programm weiter läuft */
  
  if (kill(0, SIGUSR2) == -1)	/* Signal SIGUSR2 an alle Prozesse senden */
  {
    switch (errno)
    {
    case EINVAL:
      fprintf(stderr, "ERROR: The value of the sig argument is an invalid or unsupported signal number. Tried to send SIGUSR2 to all processes\n");
      break;
    case EPERM:
      fprintf(stderr, "ERROR: The process does not have permission to send the signal to any receiving process. Tried to send SIGUSR2 to all processes\n");
      break;
    case ESRCH:
      fprintf(stderr, "ERROR: The process does not have permission to send the signal to any receiving process. Tried to send SIGUSR2 to all processes\n");
      break;
    default:
      fprintf(stderr, "ERROR: unknown error while sending SIGUSR2 to all processes");
    }
  }
  
  /* alle Proxy Prozesse beenden */
  while (sum_proxy_processes_logd > 0)
  {
    pid = waitpid(0, NULL, 0);		/* warten bis irgend ein Prozess beendet */
    if (pid < 0)
    {
      perror("Error at waiting for proxy processes to exit.\n");
    }

    sum_proxy_processes_logd--;		/* runterzählen, dass ein Proxy Prozess beendet wurde */
  }
  
  /* Signale an andere Prozesse senden um diese zu beenden */
  if (kill(summary_pid, SIGUSR1) != 0)	/* Signal SIGUSR1 an alle Prozesse senden */
  {
    switch (errno)
    {
    case EINVAL:
      fprintf(stderr, "ERROR: The value of the sig argument is an invalid or unsupported signal number. Tried to send SIGUSR1 to summary process\n");
      break;
    case EPERM:
      fprintf(stderr, "ERROR: The process does not have permission to send the signal to any receiving process. Tried to send SIGUSR1 to summary process\n");
      break;
    case ESRCH:
      fprintf(stderr, "ERROR: The process does not have permission to send the signal to any receiving process. Tried to send SIGUSR1 to summary process\n");
      break;
    default:
      fprintf(stderr, "ERROR: unknown error while sending SIGUSR1 to all processes");
    }
  }
 
   /* ist auch der summary prozess fertig, wird das programm beendet */
   if (waitpid(summary_pid, NULL, 0) != summary_pid) 
   {
     fprintf(stderr, "ERROR: waitpid() summary process\n");
     exit(1);
   }
 
   close_tcp_socket(server_socket);
   close_tcp_socket(client_socket);
   
   /*Datei schließen*/
   fclose(file);
     
  exit(0);
  } 
  else if (signo == SIGUSR1 || signo == SIGUSR2)
  {
    signal(signo, SIG_DFL);
     /* signal ignorieren */
  } 
  else {
    printf("\nreceived signal %d\n\n", signo);
  } 

  return;
}



/********************************************
 * FUNKTION
 * 
 * Name: main
 * 
 * Zweck: Hauptfunktion. Startet die Prozesse und wartet darauf, dass Clients verbinden.
 * 
 * Parameter: Startparameter
 * 
 * Globale Variablen: file, server_socket, client_socket, sum_proxy_processes
 * 	
 * Rückgabewert: 0 wenn erfolgreich
 * 	
 * 
 ********************************************/
int main( int argc, char* const argv[] )
{
  int option_char;
  unsigned short port = 0;
  unsigned int timeout_secs = 0;
  char server_host[64] = "";
  unsigned short server_port = 0;
  char delimeter = ':';			/* Trenner für host String */
  char str[70] = "";				/* Hilfsstring für host String */
  char filename[128] = "";

  pid_t proxy_pid;

  int signal_handler_registered = FALSE;

  /* Programmstart
   * Übergabeparameter werden übernommen und ausgewertet
   * frei nach dem Beispiel von http://www.gnu.org/s/hello/manual/libc/Example-of-Getopt.html#Example-of-Getopt */
  while ((option_char = getopt (argc, argv, "s:f:p:t:h")) != -1)
    switch (option_char)
    {
    case 's':
      strcpy(str, optarg);	/* Parameterstring in temporären String kopieren, um trennen vorzubereiten. */
      strcat(server_host, strtok(str, &delimeter));		/* String trennen */
      server_port = atoi(strtok(NULL, &delimeter));	/* zweiter Aufruf mit null siehe man page */
      break;
    case 'f':
      strcat(filename, optarg);
      break;
    case 'p':
      port = atoi(optarg);
      break;
    case 't':
      timeout_secs = atoi(optarg);
      break;
    case 'h':		/* nur die Hilfe anzeigen und beenden */
      print_help();
      return 0;
      break;
    case '?':
      fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt);
      return 1;
    default:
      abort ();
    }

  /* Wurden alle Parameter angegeben? */
  if (strcmp(filename, "") == 0 || port == 0 || timeout_secs == 0 || strcmp(server_host, "") == 0 || server_port == 0)
  {
    print_help();
    return 1;
  }

  /*File für Ausgabe vorbereiten*/

  /* wenn als Filename "-" angegeben ist, direkt ausgeben, ansonsten File öffnen */
  /* bei einem rückgabewert von strcmp von 0 sind die zu vergleichenden zeichenketten identisch */
  if(strcmp(filename, "-") == 0)
  {
    file = stdout;
  }
  else 
  {
    file=fopen(filename,"a");

    /* Überprüfen, ob Datei geöffnet werden konnte */
    if(file==NULL)
    {
      fprintf(stderr, "ERROR: Could not open file %s", filename);
    }
  }

  /* summary process erstellen */
  summary_pid = fork();
  if (summary_pid == 0)	/* Kind Prozess */
  {
      do_collect("summaryipc");
      exit(0);
  } 
  else if (summary_pid < 0) 
  {
    fprintf(stderr, "ERROR: fork() summary process\n");
    exit(1);
  }


  /* erstelle einen Socket auf den sich clients verbinden können */
  server_socket = server_create_tcp_socket(port);
  printf("Accepting client requests on port %d.\n", port);
    
  while (finished == FALSE)
  { 
    /* Warte auf eingehende Clientverbindungen */
    client_socket = server_accept_tcp_socket(server_socket);
    if (client_socket == 0)
    {
      perror("FEHLER eingehende Verbindung konnte nicht korrekt initialisiert werden");
    }    
    /* Wenn der client erkannt wird, blockiert server_accept_tcp_socket nicht mehr und es geht automatisch weiter  */
    
    /* logd startet einen neuen prozess und gibt diesem den socket, über den der client verbunden hat, mit */
    /* proxy process erstellen */
    proxy_pid = fork();
    if (proxy_pid == 0)	/* Kind Prozess */
    {
      do_transmit(client_socket, server_host, server_port, file, timeout_secs);
      
      return 0;
    } 
    else if (proxy_pid < 0) 
    {
      fprintf(stderr, "ERROR: fork() proxy process\n");
      exit(1);
    }
    
    /* Signalhandler registrieren nach Beispiel aus Vorlesung */
    /* Signalhandler dürfen erst nach den Forks registriert werden. */
    if (signal_handler_registered == FALSE)
    {
      if (signal(SIGINT, sig_handler) == SIG_ERR) 
      {
        fprintf(stderr, "ERROR: cannot catch SIGINT\n");
      }
      if (signal(SIGUSR1, sig_handler) == SIG_ERR) 
      {
        fprintf(stderr, "ERROR: cannot catch SIGUSR1\n");
      }
      if (signal(SIGUSR2, sig_handler) == SIG_ERR) 
      {
        fprintf(stderr, "ERROR: cannot catch SIGUSR2\n");
      }
      signal_handler_registered = TRUE;
    }
    
    /* logd speichert die anzahl der erstellten prozesse */
    sum_proxy_processes_logd = sum_proxy_processes_logd + 1;

  }

  return 0;
}